package com.acmedcare.framework.exchange.center;

import com.acmedcare.framework.exchange.center.core.Const;
import com.acmedcare.framework.exchange.center.core.PluginContainerMBean;
import com.acmedcare.framework.exchange.center.core.PluginContainerMBeanProxy;
import com.acmedcare.framework.exchange.center.core.handler.ConsumerHandlerMBean;
import com.acmedcare.framework.exchange.center.core.handler.ConsumerHandlerProxy;
import com.acmedcare.framework.exchange.center.core.handler.IAgentPublisher;
import com.acmedcare.framework.exchange.center.core.handler.PluginCommandHandler;
import com.acmedcare.framework.exchange.center.core.handler.PluginHandlerMBean;
import com.acmedcare.framework.exchange.center.core.handler.PluginHandlerProxy;
import com.acmedcare.framework.exchange.center.core.handler.StageCommandHandler;
import com.acmedcare.framework.exchange.center.core.storage.StageRepository;
import com.acmedcare.framework.exchange.center.entity.dto.Hdfs;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

@Configuration
@EnableConfigurationProperties(Hdfs.class)
@Slf4j
public class OpenApiServer {

  /**
   * Date格式化字符串
   */
  public static final String DATE_FORMAT = "yyyy-MM-dd";
  /**
   * DateTime格式化字符串
   */
  public static final String DATETIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
  /**
   * Time格式化字符串
   */
  public static final String TIME_FORMAT = "HH:mm:ss";

  @Bean
  public PluginContainerMBean pluginContainer(Hdfs hdfs) {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(PluginContainerMBean.class);
    enhancer.setCallback(new PluginContainerMBeanProxy(hdfs));
    return (PluginContainerMBean) enhancer.create();
  }

  @Bean
  public PluginHandlerMBean pluginHandler() {

    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(PluginHandlerMBean.class);
    enhancer.setCallback(new PluginHandlerProxy());
    return (PluginHandlerMBean) enhancer.create();
  }

  @Bean
  public ConsumerHandlerMBean consumerHandler() {

    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(ConsumerHandlerMBean.class);
    enhancer.setCallback(new ConsumerHandlerProxy());
    return (ConsumerHandlerMBean) enhancer.create();
  }

  @Bean
  public Queue queue() {

    String pluginChannel =
        "fanout.exchange.center.open.api.plugin." + System.getenv(Const.GroupName) + "."
            + UUID
            .randomUUID().toString();
    return new Queue(pluginChannel, false,false,true);
  }

  @Bean
  public FanoutExchange exchange() {

    return new FanoutExchange(Const.PluginExchangeName);
  }

  @Bean
  public Binding binding(Queue queue, FanoutExchange exchange) {

    return BindingBuilder.bind(queue).to(exchange);
  }

  @Bean
  @ConditionalOnMissingBean
  public AmqpTemplate rabbitTemplate(ConnectionFactory connectionFactory) {

    final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
    return rabbitTemplate;
  }

  @Bean
  SimpleMessageListenerContainer container(ConnectionFactory connectionFactory,
      MessageListenerAdapter listenerAdapter, Queue queue) {

    SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
    container.setConnectionFactory(connectionFactory);
    container.setQueueNames(queue.getName());
    container.setMessageListener(listenerAdapter);
    return container;
  }

  @Bean
  MessageListenerAdapter listenerAdapter(PluginCommandHandler commandHandler) {

    return new MessageListenerAdapter(commandHandler, "receive");
  }

  @Bean
  public IAgentPublisher agentPublisher(RabbitTemplate rabbitTemplate, ObjectMapper objectMapper) {
    return (exchangeName, obj) -> {
      rabbitTemplate.setExchange(exchangeName);
      String dataString = "";
      try {

        dataString = objectMapper.writeValueAsString(obj);
        rabbitTemplate.convertAndSend(dataString);
      } catch (Exception e) {
        log.error(String
            .format("通道：%s消息:%s,发送失败,异常信息：%s", exchangeName, dataString, e.getMessage()));
      }
    };
  }

  @Bean
  public StageCommandHandler stageCommandHandler(PluginContainerMBean pluginContainer,
      ObjectMapper objectMapper, StageRepository stageRepository) {

    return new StageCommandHandler(objectMapper, pluginContainer, stageRepository);
  }

  @Bean
  @Primary
  public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
    //noinspection Duplicates
    return builder -> builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(
        DateTimeFormatter.ofPattern(DATETIME_FORMAT)))
        .serializerByType(LocalDate.class,
            new LocalDateSerializer(DateTimeFormatter.ofPattern(DATE_FORMAT)))
        .serializerByType(LocalTime.class,
            new LocalTimeSerializer(DateTimeFormatter.ofPattern(TIME_FORMAT)))
        .deserializerByType(LocalDateTime.class,
            new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DATETIME_FORMAT)))
        .deserializerByType(LocalDate.class,
            new LocalDateDeserializer(DateTimeFormatter.ofPattern(DATE_FORMAT)))
        .deserializerByType(LocalTime.class,
            new LocalTimeDeserializer(DateTimeFormatter.ofPattern(TIME_FORMAT)));
  }
}
